<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>任务树形表格与甘特图表</title>
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <link rel="stylesheet" type="text/css" href="https://www.jeasyui.com/easyui/themes/icon.css">
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.min.js"></script>
    <script type="text/javascript" src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>

    <script type="text/javascript" src="https://www.jeasyui.com/easyui/plugins/jquery.edatagrid.min.js"></script>


    <script src="https://cdn.jsdelivr.net/npm/luxon@1.27.0/build/global/luxon.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.0.0"></script>
    <script type="importmap">
        {
          "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.module.js",
            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/"
          }
        }
    </script>
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        // 创建场景
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0xbfd1e5);

        // 创建相机
        const camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );

        camera.position.set(0, 50, 30);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
        // 创建渲染器
        const renderer = new THREE.WebGLRenderer({
            antialias: true,   // 开启抗锯齿
            canvas: $('#threejsCanvas')[0]
        });
        const controls = new OrbitControls(camera, renderer.domElement);
        const loader = new GLTFLoader();
        const cube = new THREE.Mesh(
            new THREE.BoxGeometry(1, 1, 1),
            new THREE.MeshBasicMaterial({ color: 0x00ff00 })
        );
        const cube2 = new THREE.Mesh(
            new THREE.BoxGeometry(1, 1, 1),
            new THREE.MeshBasicMaterial({ color: 0xff0000 })
        );
        cube2.position.x = 2;
        scene.add(cube, cube2);

        // let angle = 0;
        function animate() {
            requestAnimationFrame(animate);
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;

            // const radius = 3;
            // angle += 0.01;
            // camera.position.x = radius* Math.sin(angle);
            // camera.position.z = radius* Math.cos(angle);
            // camera.lookAt(new THREE.Vector3(0, 0, 0));
            renderer.render(scene, camera);
        }
        animate()


    </script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
        }

        #header {
            padding: 10px;
            background: #f3f3f3;
            border-bottom: 1px solid #ccc;
        }

        #treeTable {
            width: 100%;
            height: 100%;
        }

        #ganttChart {
            width: 100%;
            height: 100%;
            border: 1px solid red
        }

        #ganttBox {
            position: relative;
            /* padding: 5px; */
            /* 添加5px的外扩 */
            box-sizing: border-box;
            /* 确保padding不会增加元素的总宽度和高度 */
            width: 100%;
            height: 100%;
        }

        .easyui-layout {
            height: 100%;
            overflow: auto;
        }

        .easyui-layout .panel-header {
            padding: 5px;
        }

        #toolbar {
            padding: 5px;
        }

        #columnMenu {
            width: 150px;
        }

        #ganttToolbar {
            background-color: #f3f3f3;
            padding: 5px;
        }

        .icon-play {
            background-image: url('images/icons/icon-play.png') !important;
            /* 调整尺寸和位置 */
            background-position: center center;
            background-repeat: no-repeat;
            width: 16px;
            height: 16px;
        }

        .icon-pause {
            background-image: url('images/icons/icon-pause.png') !important;
            /* 调整尺寸和位置 */
            background-position: center center;
            background-repeat: no-repeat;
            width: 16px;
            height: 16px;
        }

        .gantt-toolbar-separator {
            /* 工具条分隔符 样式 */
            display: inline-block;
            height: 24px;
            border-left: 1px solid #ccc;
            margin: 0 8px;
            vertical-align: middle;
        }

        #movingIcon {
            /* 时间指针 */
            position: absolute;
            width: 3px;
            height: 10px;
            background-color: red;
            border-radius: 50%;
            z-index: 1500;
        }

        #threejsBox {
            position: relative;
            width: 100%;
            height: 100%;
        }

        #threejsCanvas {
            width: 100%;
            height: 100%;
            background-color: #000;
            /* 设置背景颜色以便于查看 */
            border: 1px solid red;
        }
    </style>
</head>

<body>
    <div class="easyui-layout" fit="true">
        <div id="header" data-options="region:'north',split:true,border:false" style="height:60px;">

            <h2 style="margin:0;">任务树形表格与甘特图表</h2>

        </div>
        <div data-options="region:'west',split:true,title:'任务列表',width:300">
            <table id="treeTable" class="easyui-treegrid" data-options="
                    method: 'get',
                    idField: 'id',
                    treeField: 'name',
                    fitColumns: true,
                    singleSelect: true,
                    toolbar: '#toolbar',
                    fit: true
                ">
                <thead>
                    <tr>
                        <th data-options="field:'name',width:200">任务名称</th>
                        <th data-options="field:'start',width:150,align:'center'">开始时间</th>
                        <th data-options="field:'finish',width:150,align:'center'">结束时间</th>
                        <th data-options="field:'duration',width:150,align:'center'">持续时间</th>
                    </tr>
                </thead>
            </table>
            <div id="toolbar">
                <input type="file" id="xmlFile" accept=".xml" onchange="loadXMLFile(this)" style="display:none;" />
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-add" plain="true"
                    onclick="triggerFileUpload()">上传 XML 文件</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-ok" plain="true"
                    onclick="$('#columnMenu').menu('show', {left: event.pageX, top: event.pageY});">列显示</a>
            </div>
        </div>
        <div data-options="region:'center',title:'甘特图表'" style="overflow: hidden;">
            <div id="ganttToolbar">
                <span style="vertical-align: middle;"> 当前时刻：</span>
                <input type="text" id="timerShow" value="2023-07-01" style="width: 130px; margin: 0 5px;"></input>
                <span class="gantt-toolbar-separator"></span>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-play" plain="true"
                    onclick="startTimer()">播放</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-pause" plain="true"
                    onclick="pauseTimer()">暂停</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" iconCls="icon-reload" plain="true"
                    onclick="resetTimer()">重置</a>
                <span class="gantt-toolbar-separator"></span>
                <span style="vertical-align: middle;">播放倍速：</span><input type="number" id="speedInput" value="100"
                    min="0.1" max="100000" step="1" style="width: 70px; margin: 0 5px;">
                <span style="vertical-align: middle;">10秒/秒</span>
                <span class="gantt-toolbar-separator"></span>
                <a href="javascript:void(0)" class="easyui-linkbutton" plain="true" iconCls="icon-add"
                    onclick="changeSpeed(10)">10X</a>
                <a href="javascript:void(0)" class="easyui-linkbutton" plain="true" iconCls="icon-remove"
                    onclick="changeSpeed(0.1)">10X</a>
                <span class="gantt-toolbar-separator"></span>
            </div>


            <div class="easyui-layout" data-options="fit:true">
                <div data-options="region:'north',split:true" style="height:57px">
                    <div style="width: 100%; height:100%;overflow:auto; ">
                        <div id="ganttBox">
                            <canvas id="ganttChart"></canvas>
                            <div id="movingIcon"></div>
                        </div>
                    </div>
                </div>
                <div data-options="region:'center'">
                    <div style="width: 100%; height: 93%;overflow:auto;">
                        <div id="threejsBox">
                            <canvas id="threejsCanvas"></canvas>
                        </div>

                    </div>

                </div>
            </div>


        </div>
    </div>

    <div id="columnMenu" class="easyui-menu" style="width:150px;">
        <div data-options="name:'name',iconCls:'icon-ok'">任务名称</div>
        <div data-options="name:'start',iconCls:'icon-ok'">开始时间</div>
        <div data-options="name:'finish',iconCls:'icon-ok'">结束时间</div>
        <div data-options="name:'duration',iconCls:'icon-ok'">持续时间</div>
    </div>

    <script>
        // 触发文件上传
        function triggerFileUpload() {
            document.getElementById('xmlFile').click();
        }

        // 读取上传的 XML 文件并加载到树形表格和甘特图表
        function loadXMLFile(input) {
            const file = input.files[0];

            if (!file) {
                alert('请选择一个 XML 文件');
                return;
            }

            const reader = new FileReader();
            reader.onload = function (event) {
                const xmlText = event.target.result;
                const treeData = parseXMLToTreeData(xmlText);
                $('#treeTable').treegrid('loadData', treeData);
                renderGanttChart(treeData);
            };

            reader.readAsText(file);
        }

        // 解析 XML 文件并转换为 EasyUI 树形表格数据
        function parseXMLToTreeData(xmlText) {
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(xmlText, 'application/xml');

            const tasks = [];
            const taskNodes = xmlDoc.getElementsByTagName('Task');

            // 安全解析函数
            const getChildText = (node, tagName) => {
                const elements = node.getElementsByTagName(tagName);
                return elements.length > 0 ? elements[0].textContent : '';
            };

            for (let i = 0; i < taskNodes.length; i++) {
                const taskNode = taskNodes[i];
                const task = {
                    id: getChildText(taskNode, 'ID'),
                    name: getChildText(taskNode, 'Name'),
                    start: getChildText(taskNode, 'Start'),
                    finish: getChildText(taskNode, 'Finish'),
                    outlineLevel: parseInt(getChildText(taskNode, 'OutlineLevel')) || 1,
                    duration: getChildText(taskNode, 'Duration'),
                    children: []
                };
                tasks.push(task);
            }

            // 转换持续时间为“天 小时”格式
            tasks.forEach(task => {
                task.duration = convertDuration(task.duration);
            });

            // 构建树形结构
            const treeData = [];
            const stack = [];

            tasks.forEach(task => {
                while (stack.length > 0 && stack[stack.length - 1].outlineLevel >= task.outlineLevel) {
                    stack.pop();
                }

                if (stack.length === 0) {
                    treeData.push(task);
                } else {
                    stack[stack.length - 1].children.push(task);
                }

                stack.push(task);
            });

            // 去掉 treeData 的第一个元素
            if (treeData.length > 0) {
                treeData.shift();
            }

            return treeData;
        }

        // 将 ISO 8601 格式的持续时间转换为“天 小时”格式
        function convertDuration(duration) {
            if (!duration) return '';

            const durationRegex = /PT(\d+)H(\d+)M(\d+)S/;
            const match = duration.match(durationRegex);

            if (!match) return '';

            const hours = parseInt(match[1], 10);
            const days = Math.floor(hours / 24);
            const remainingHours = hours % 24;

            if (days > 0) {
                return `${days}天 ${remainingHours}小时`;
            } else {
                return `${remainingHours}小时`;
            }
        }

        //递归遍历树形表格数据treeData

        function traverseTreeData(treeData) {
            const teskLabels = [];
            const teskColors = ['rgba(0, 0, 255, 0.3)',
                'rgba(255, 0, 0, 0.5)',
                'rgba(0, 255, 0, 0.5)',
                'rgba(255, 255, 0, 0.5)',
                'rgba(0, 255, 255, 0.5)',
                'rgba(255, 0, 255, 0.5)',
                'rgba(128, 0, 0, 0.5)',
                'rgba(128, 128, 0, 0.5)',
                'rgba(128, 0, 128, 0.5)',
                'rgba(0, 128, 128, 0.5)',
                'rgba(0, 128, 0, 0.5)',
                'rgba(0, 0, 128, 0.5)'];

            function getChildren(item) {

                teskLabels.push({
                    name: item.name, start: item.start, finish: item.finish,
                    color: teskColors[item.outlineLevel]
                });


                if (item.children && item.children.length > 0 && item.state === 'open') {
                    item.children.forEach(child => {
                        getChildren(child);
                    });
                }


            }


            getChildren(treeData[0]);

            return teskLabels
        }

        // 渲染甘特图表
        let ganttChart = null;
        let tasks = [];
        function renderGanttChart(treeData) {
            console.log(treeData);
            const ctx = document.getElementById('ganttChart').getContext('2d');
            // teskLabels = ['任务1', '任务2', '任务3'];
            tasks = traverseTreeData(treeData);

            if (ganttChart) {
                ganttChart.destroy();
            }
            $('#ganttBox').css({
                width: `100%`,
                height: 30 * tasks.length + 20

            });
            ganttChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: tasks.map(task => task.name),

                },
                options: {
                    indexAxis: 'y',
                    aspectRatio: false,    // 宽度自适应
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'day',
                                tooltipFormat: 'yyyy-MM-dd'
                            },
                            min: getMinDate(tasks),
                            max: getMaxDate(tasks),
                            position: 'top'
                        },
                        y: {
                            ticks: {
                                autoSkip: false
                            }
                        }
                    },
                    plugins: {
                        legend: {
                            display: false
                        },
                        title: {
                            display: false,
                            text: '横道图示例'
                        }
                    },
                    elements: {
                        bar: {
                            display: false
                        }
                    }
                },
                plugins: [{
                    id: 'customGantt',
                    afterDraw: function (chart) {

                        // console.log(chart);
                        const ctx = chart.ctx;
                        const xScale = chart.scales.x;
                        const yScale = chart.scales.y;

                        tasks.forEach((task, index) => {
                            // 转换日期为Luxon对象
                            const start = luxon.DateTime.fromISO(task.start);
                            const finish = luxon.DateTime.fromISO(task.finish);
                            // console.log(task.name, start, finish);

                            const x = xScale.getPixelForValue(start);
                            const y = yScale.getPixelForValue(task.name) - 12;
                            const width = xScale.getPixelForValue(finish) - x;
                            const height = 24;

                            ctx.fillStyle = task.color;
                            ctx.fillRect(x, y, width, height);

                            ctx.fillStyle = '#000';
                            ctx.font = '12px Arial';
                            ctx.textAlign = 'center';
                            ctx.fillText(task.name, x + width / 2, y + height / 2 + 4);
                        });
                    }
                }]
            });



            initTimer(tasks)
            // renderFloatingTimeline(tasks);

        }




        /////////////////////////////
        // 时间指针功能函数定义
        //////////////////////////////

        // 初始化时间指针
        const movingIcon = document.getElementById('movingIcon');
        const timerElement = document.getElementById('timerShow');
        let currentDate;
        let isRunning = false;
        let timerInterval;



        // 初始化时间指针
        function initTimer(tasks) {
            currentDate = luxon.DateTime.fromISO(getMinDate(tasks));
            console.log(currentDate);
            timerElement.value = currentDate.toFormat('yyyy-MM-dd HH:mm');
            updateMovingIcon();
        }

        // 更新时间指针
        function updateTimer(tasks) {
            const multSpeed = $('#speedInput').val();
            currentDate = currentDate.plus({ seconds: 10 * multSpeed });
            console.log(currentDate);
            if (currentDate.ts > luxon.DateTime.fromISO(getMaxDate(tasks)).ts) {
                initTimer(tasks);
                return;
            }
            timerElement.value = currentDate.toFormat('yyyy-MM-dd HH:mm');

            updateMovingIcon();  // 更新移动图标位置
            // updateFloatingTimeline(); // 更新时间轴位置

        }

        // 更新移动图标位置
        function updateMovingIcon() {
            if (!ganttChart) return;
            const chart = ganttChart;
            const xScale = chart.scales.x;
            const x = xScale.getPixelForValue(currentDate.ts);
            movingIcon.style.left = `${x - 1}px`;
            movingIcon.style.top = `${chart.chartArea.top - 23}px`;
            movingIcon.style.height = `${chart.chartArea.height + 30}px`;
        }


        // 启动按钮函数
        function startTimer() {
            if (!isRunning) {
                timerInterval = setInterval(() => updateTimer(tasks), 10);
                isRunning = true;

            }
        }

        //  暂停按钮函数
        function pauseTimer() {
            if (isRunning) {
                clearInterval(timerInterval);
                isRunning = false;
            }
        }

        // 重置按钮函数
        function resetTimer() {
            clearInterval(timerInterval);
            initTimer(tasks);
            isRunning = false;

        }

        // 获取任务的最小开始时间
        function getMinDate(tasks) {
            return tasks[0].start;
        }
        // 获取任务的最大结束时间
        function getMaxDate(tasks) {
            return tasks[0].finish;
        }





        //////////////////////////////
        // 甘特图toolbar功能函数定义
        //////////////////////////////




        // 放大功能
        function zoomIn() {
            console.log('放大');
            // 在这里添加放大逻辑
        }

        // 缩小功能
        function zoomOut() {
            console.log('缩小');
            // 在这里添加缩小逻辑
        }

        // 重置功能
        function resetGantt() {
            console.log('重置');
            // 在这里添加重置逻辑
        }

        // 改变速度功能
        function changeSpeed(factor) {
            const speedInput = document.getElementById('speedInput');
            let currentSpeed = parseFloat(speedInput.value);

            // 计算新速度（保留1位小数）
            let newSpeed = parseFloat((currentSpeed * factor).toFixed(1));

            // 边界限制（0.1-100000）
            newSpeed = Math.max(0.1, Math.min(newSpeed, 100000));

            // 更新输入框
            speedInput.value = newSpeed;

        }

        $(document).ready(function () {

            /////////////////////////
            // 初始化树形表格为空
            ///////////////////////////
            $('#treeTable').treegrid('loadData', []);
            $('#treeTable').treegrid({
                // 其他配置...
                onExpand: function (row) {
                    renderGanttChart($('#treeTable').treegrid('getData'));
                },
                onCollapse: function (row) {
                    renderGanttChart($('#treeTable').treegrid('getData'));
                }
            });

            // 初始化列显示菜单
            $('#columnMenu').menu({
                onClick: function (item) {
                    const fieldName = item.name;
                    const columnOpt = $('#treeTable').treegrid('getColumnOption', fieldName);
                    const isVisible = !columnOpt.hidden;
                    // console.log($(item.target));
                    // console.log(fieldName, isVisible);

                    if (isVisible) {
                        $('#treeTable').treegrid('hideColumn', fieldName);
                        $(item.target).find('.icon-ok').hide();
                    } else {
                        $('#treeTable').treegrid('showColumn', fieldName);
                        $(item.target).find('.icon-ok').show();
                    }
                }
            });


            //////////////////
            // 初始化甘特图
            //////////////////
            // const ctx = document.getElementById('ganttChart').getContext('2d');
            // let ganttChart;

        });
    </script>

</body>

</html>